/*
 * @Author: your name
 * @Date: 2021-11-02 10:12:51
 * @LastEditTime: 2021-11-23 14:14:22
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: /my-draw/src/utils/draw/draw.ts
 */

import { IDraw } from "./draw.API";
import type { Object, FeatureStore, FunctionObj } from './draw.constant'
import { EMode, ECursor } from './draw.constant'
import type {
  Feature,
  GeoJSON,
  FeatureCollection,
  Geometry,
  GeometryCollection,
  Point,
  Position,
  MultiPoint,
  MultiLineString,
  MultiPolygon,
  Polygon,
  LineString,
  GeoJsonProperties,
  BBox
} from './draw.constant';
import Style from './draw.style'
import { MyMap } from './myMap'

import bboxPolygon from '@turf/bbox-polygon'
import distance from '@turf/distance'
import buffer from '@turf/buffer'
import {
  point as turfPoint,
  multiPoint as turfMultiPoint,
  geometry as turfGeometry,
  geometryCollection as turfGeometryCollection,
  lineString as turfLineString
} from '@turf/helpers'

export class Draw extends MyMap implements IDraw {

  /**
   * 构造函数
   * @param mapbox 地图对象
   */
  constructor(mapbox: any) {
    super(mapbox)
    mapbox.on('mousemove', this.cursorEvent) // 指针
  }

  /****************************************************************
   * hooks 事件钩子
   ****************************************************************/

  private initHooks = (): FunctionObj => {
    return <FunctionObj>{
      before: () => console.log('绘制开始前'),
      after: () => console.log('绘制结束后'),
      draw: () => console.log('绘制中')
    }
  }
  private hooks: FunctionObj = this.initHooks()

  /**
   * 挂载事件
   * @param {'before' | 'after' | 'draw'} type 开始前、绘制中、结束后
   * @param callback 回调
   */
  public on = (type: 'before' | 'after' | 'draw', callback: Function): Function => {
    return this.hooks[type] = callback
  }

  /**
   * 解绑事件
   * @param {'before' | 'after' | 'draw' | undefined} type 单独解绑或全部解绑
   */
  public off = (type?: 'before' | 'after' | 'draw'): void => {
    const tempObj = this.initHooks()
    type
      ? (this.hooks[type] = tempObj[type])
      : (this.hooks = tempObj)
  }


  /****************************************************************
   * 私有方法
   ****************************************************************/

  /**
   * @description: 添加到 store
   * @param {string} id
   * @param {GeoJSON} feature
   * @return {false | GeoJSON}
   */
  private addToStore = (id: string, feature: GeoJSON): false | GeoJSON => {
    return !!!this.featureStore[id] && (this.featureStore[id] = feature)
  }

  /**
   * @description: 添加到 store && layer
   * @param {string} id
   * @param {GeoJSON} feature
   * @param {*} type
   */
  private addToStoreAndLayer = (id: string, feature: GeoJSON, type: 'line' | 'circle' | 'fill'): void => {
    this.addLayer[type](id, feature) // layer
    this.addToStore(id, feature) // store
  }

  /**
   * @description: 更新 store && source
   * @param {string} id
   * @param {GeoJSON} feature
   */
  private updateStoreAndSource = (id: string, feature: GeoJSON): void => {
    !!this.featureStore[id] && (this.featureStore[id] = feature) // store
    this.updateSource(id, feature) //source
  }

  /**
   * @description: 完全删除要素
   * @param {string} id
   */
  private delStoreAndLayerAndSource = (id: string): boolean => {
    return !!this.featureStore[id] && delete this.featureStore[id] && this.removeLayerAndSource(id)
  }

  /**
   * @description: 改变鼠标指针
   * @param {ECursor} type
   */
  private handleChangeCursor = (leave: ECursor, enter?: ECursor): void => {
    this.mapbox.off('mousemove', this.cursorEvent) // 解绑
    this.currentCursor.leave = leave // 移出
    !!enter && (this.currentCursor.enter = enter) // 移入
    this.mapbox.on('mousemove', this.cursorEvent) // 绑定
  }

  /**
   * @description: 进入绘制
   * @param {any} e
   */
  private drawToMap = (e: any): void => {
    this.hooks.draw() // 绘制触发事件

    console.log('绘制模式：' + this.currentMode)

    // 提前中断绘制条件：有 layer 层 && 该 layer 层有相同位置的点
    if (!!this.mapbox.getLayer(this.drawLayerName) && !!this.mapbox.queryRenderedFeatures(e.point, { layers: [this.drawLayerName] }).length) {
      return this.endDraw()
    }

    const lnglat: Position = [e.lngLat.lng, e.lngLat.lat]; // 点击坐标

    switch (this.currentMode) {
      case EMode.DRAW_POINT: this.drawPoint(lnglat); break; // 绘点
      case EMode.DRAW_POLYLINE: this.drawPolyLine(lnglat); break; // 绘线
      case EMode.DRAW_POLYGON: this.drawPolygon(lnglat); break; // 绘面
      case EMode.DRAW_RECT: this.drawRect(lnglat); break; // 绘矩形
      case EMode.DRAW_CIRCLE: this.drawCircle(lnglat); break; // 绘圆
    }
  }

  /**
   * 绘制点
   * @param lnglat
   */
  private drawPoint = (lnglat: Position): void => {
    const id = this.drawLayerName // id

    // 生成 feature
    const points: Feature = <Feature>turfMultiPoint([lnglat])

    // 有无 layer
    if (!!!this.mapbox.getLayer(id)) {
      this.addToStoreAndLayer(id, points, 'circle') // 无则新增
    } else {
      // 有则更新
      const { _data: geometry } = this.mapbox.getSource(id)
      geometry.geometry.coordinates.push(lnglat)
      this.updateStoreAndSource(id, geometry)
    }
  }

  /**
   * 绘制线
   * @param lnglat
   */
  private drawPolyLine = (lnglat: Position): void => {
    const id = this.drawLayerName // id

    // 生成 feature
    const getFeatureFn = (): Geometry => (<Geometry>{
      type: 'GeometryCollection',
      geometries: <Geometry[]>[
        <MultiPoint>{ type: "MultiPoint", coordinates: [lnglat] },
        <LineString>{ type: 'LineString', coordinates: [lnglat] }
      ],
    })

    const followPoint = id + '_follow_point' // 附属点

    // 有无 layer
    if (!!!this.mapbox.getLayer(id)) {
      // 无则新增
      this.addToStoreAndLayer(id, getFeatureFn(), 'line') // 主体
      this.addToStoreAndLayer(followPoint, getFeatureFn(), 'circle') // 附属点
    } else {
      // 有则更新
      const { _data: feature } = this.mapbox.getSource(id)
      feature.geometries.map((v: MultiPoint | LineString) => v.coordinates.push(lnglat))
      this.updateStoreAndSource(id, feature)
      this.updateStoreAndSource(followPoint, feature)
    }

    // 辅助线
    this.addLayer.line('tempLine', getFeatureFn()) // 添加辅助线
    this.updateSource('tempLine', getFeatureFn()) // 更新数据
    this.events.mousemove = this.tempLine // 更新总线
    this.mapbox.on('mousemove', this.events.mousemove) // 鼠标移动事件
  }

  /**
   * 绘制面
   * @param lnglat
   */
  private drawPolygon = (lnglat: Position): void => {
    const id = this.drawLayerName // id

    // 生成 feature
    const getFeatureFn = (): Geometry => (<Geometry>{
      type: 'GeometryCollection',
      geometries: <Geometry[]>[
        <Polygon>{ type: 'Polygon', coordinates: [[lnglat]] },
        <MultiPoint>{ type: "MultiPoint", coordinates: [lnglat] },
        <LineString>{ type: 'LineString', coordinates: [lnglat] }
      ],
    })

    const followPoint = id + '_follow_point' // 附属点
    const followLine = id + '_follow_line' // 附属线

    // 有无 layer
    if (!!!this.mapbox.getLayer(id)) {
      // 无则新增
      this.addToStoreAndLayer(id, getFeatureFn(), 'fill') // 主体
      this.addToStoreAndLayer(followLine, getFeatureFn(), 'line') // 附属线
      this.addToStoreAndLayer(followPoint, getFeatureFn(), 'circle') // 附属点
    } else {
      // 有则更新
      const { _data: feature } = this.mapbox.getSource(id)
      feature.geometries.map((v: MultiPoint | LineString | Polygon) => {

        v.type === 'Polygon' // 多边形
          ? (<Polygon>v).coordinates[0].push(lnglat) // 三维数组
          : (<MultiPoint | LineString>v).coordinates.push(lnglat) // 二维数组
      })
      this.updateStoreAndSource(id, feature)
      this.updateStoreAndSource(followPoint, feature)
      this.updateStoreAndSource(followLine, feature)
    }

    // 辅助线
    this.addLayer.line('tempLine', getFeatureFn()) // 添加辅助线
    this.events.mousemove = this.tempLine // 更新总线
    this.mapbox.on('mousemove', this.events.mousemove) // 鼠标移动事件
  }

  /**
   * @description: 绘制矩形
   * @param {Position} originDot
   */
  private drawRect = (originDot: Position): void => {
    const id = this.drawLayerName // id

    // 生成矩形
    const tempRect = (e: any): void => {
      const { lng, lat } = e.lngLat // 后面两个坐标

      const bbox: BBox = [<number>originDot[0], <number>originDot[1], lng, lat] // 生成 bbox

      const rectangle: Feature = <Feature>(bboxPolygon(bbox)) // 生成矩形

      if (!rectangle) return
      console.log(rectangle);


      !!!this.mapbox.getLayer(id)
        ? this.addToStoreAndLayer(id, rectangle, 'fill') // 无则新增
        : this.updateStoreAndSource(id, rectangle) // 有则更新

      const followLine = id + '_follow_line'
      !!!this.mapbox.getLayer(followLine)
        ? this.addToStoreAndLayer(followLine, rectangle, 'line') // 无则新增
        : this.updateStoreAndSource(followLine, rectangle) // 有则更新

      const followPoint = id + '_follow_point' // 附属点
      !!!this.mapbox.getLayer(followPoint)
        ? this.addToStoreAndLayer(followPoint, rectangle, 'circle') // 无则新增
        : this.updateStoreAndSource(followPoint, rectangle) // 有则更新
    }

    this.events.mousemove = tempRect // 更新总线
    this.mapbox.off('click', this.events.click)
    this.events.click = this.endDraw // 再次点击结束绘制
    this.mapbox.on('click', this.events.click)
    this.mapbox.on('mousemove', this.events.mousemove) // 鼠标移动事件
  }

  /**
   * @description: 绘制圆
   * @param {Position} originDot
   */
  private drawCircle = (originDot: Position): void => {
    const id = this.drawLayerName // id
    const followPoint = id + '_follow_point' // 附属点
    // const followLine = id + '_follow_line' // 附属线

    const point: Feature = <Feature>(turfPoint(originDot)) // 生成点
    const followLineFeature: Feature = { type: 'Feature', geometry: { type: 'LineString', coordinates: [originDot] }, properties: {} }

    this.addLayer.line('tempLine', followLineFeature) // 添加辅助线
    !!!this.mapbox.getLayer(followPoint) && this.addToStoreAndLayer(followPoint, point, 'circle') // 附属点

    // 生成圆
    const tempCircle = (e: any): void => {
      const lnglat: Position = [e.lngLat.lng, e.lngLat.lat]
      const radius: number = distance(originDot, lnglat) // 生成半径

      const circle: Feature = <Feature>(buffer(point, radius)) // 根据圆心、半径，生成圆

      this.tempLine(e) // 辅助线
      if (!circle) return

      !!!this.mapbox.getLayer(id)
        ? this.addToStoreAndLayer(id, circle, 'fill') // 无则新增
        : this.updateStoreAndSource(id, circle) // 有则更新
    }

    this.events.mousemove = tempCircle // 更新总线
    this.mapbox.off('click', this.events.click)
    this.events.click = this.endDraw // 再次点击结束绘制
    this.mapbox.on('click', this.events.click)
    this.mapbox.on('mousemove', this.events.mousemove) // 鼠标移动事件
  }

  /**
   * 辅助线
   */
  private tempLine = (e: any): void => {
    const lnglat: Position = [e.lngLat.lng, e.lngLat.lat] // 坐标
    const { _data: feature } = this.mapbox.getSource('tempLine') // 取旧值
    const { type } = feature
    // console.log(feature)
    type === 'GeometryCollection' // 跟随鼠标
      ? feature.geometries.map((v: MultiPoint | LineString | Polygon) => (v.type === 'LineString') && (v.coordinates[1] = lnglat))
      : feature.geometry.coordinates[1] = lnglat
    this.updateSource('tempLine', feature) // 更新
  }

  /**
   * 闭合图形
   */
  private closePolygon = (): void => {
    const { currentMode, drawLayerName } = this
    if (!this.mapbox.getSource(drawLayerName)) return
    const { _data: feature } = this.mapbox.getSource(drawLayerName)
    switch (currentMode) {
      case EMode.DRAW_POLYGON: // 闭合多边形
        feature.geometries.map((v: MultiPoint | LineString | Polygon) =>
          v.type === 'LineString' && v.coordinates.push(v.coordinates[0])
        )
        this.updateStoreAndSource(drawLayerName + '_follow_line', feature)
        break;
      case EMode.DRAW_CIRCLE: // 闭合圆形
        this.addToStoreAndLayer(drawLayerName + '_follow_line', feature, 'line')
        break;
    }
  }

  /****************************************************************
  * 接口方法
  ****************************************************************/

  /**
   * @description: 开始绘制
   * @param {EMode} mode
   * @param {number} id
   * @return {string}
   */
  public startDraw = (mode: EMode, id: number = new Date().getTime()): string => {
    this.endDraw() // 开始前先清除事件
    if (!this.mapbox.loaded() || !mode.includes('draw')) return '' // 地图未加载 or 不是绘制模式
    this.hooks.before() // 开始前事件
    this.currentMode = mode // 模式切换
    this.drawLayerName = mode + '_' + id// name
    this.handleChangeCursor(ECursor.CROSSHAIR) // 鼠标指针
    this.mapbox.on('click', this.events.click) // 触发绘制
    this.mapbox.on('contextmenu', this.events.contextmenu) // 右键退出绘制
    return this.drawLayerName // 返回完整 id
  }

  /**
    * 关闭绘制；
    */
  public endDraw = (): void => {
    console.log('退出绘制')

    // 闭合多边形
    this.closePolygon()

    this.currentMode = EMode.STATIC // 模式切换
    this.drawLayerName = '' // id 重置

    // 事件解绑
    Object.keys(this.events).map((key: string) => {
      this.mapbox.off(key, this.events[key])
    })
    this.events = this.initEvents() // 初始化事件总线

    this.removeLayerAndSource('tempLine'); // 移除辅助线
    this.handleChangeCursor(ECursor.DEFAULT) // 鼠标指针

    this.hooks.after() // 结束事件
  }

  public setFeatureCollection = (geojson: GeoJSON): void => { }

  /**
   * 根据 id 获取要素数据
   * @param {string} id  source id
   * @returns {GeoJSON | undefined} 返回一个 Feature 或 无结果
   */
  public getFeatureById = (id: string): GeoJSON | undefined => {
    return this.featureStore[id]
  }

  /**
   * 获取全部绘制数据
   * @returns
   */
  public getAll = (): FeatureStore => {
    const tempArr = Object.keys(this.featureStore).filter((v: string) => !v.includes('follow'))
    const tempObj: FeatureStore = {}
    tempArr.map((v: string) => tempObj[v] = this.featureStore[v])
    return tempObj
  }

  /**
   * 根据 id 删除要素
   * @param id
   */
  public delFeatureById = (id: string): string => {
    return this.delStoreAndLayerAndSource(id) ? id : ''
  }

  /**
   * 清空绘制要素
   */
  public clearFeatureCollection = (): void => {
    this.endDraw()
    Object.keys(this.featureStore).map((v: string) => this.delStoreAndLayerAndSource(v))
  }

  /**
   * @description: 删除模式
   * @param {*} void
   */
  public deleteMode = (): void => {
    this.endDraw()
    const { length } = Object.keys(this.featureStore)
    if (!!!length) return console.log('地图上没有可删除的要素')

    this.currentMode = EMode.DELETE; // 删除模式

    this.handleChangeCursor(ECursor.NW_RESIZE) // 鼠标指针

    // 删除事件
    const deleteEvent = (e: any): void => {
      const layers: Array<string> = Object.keys(this.featureStore) // 读取图层。这里不能排除 follow ，删除可以通过 follow

      if (!!!layers.length) return console.log('地图上没有可删除的要素')

      const features = this.mapbox.queryRenderedFeatures(e.point, { layers })
      if (!!!features.length) return

      const ids: Array<string> = [...new Set(
        [...features.map((v: any) => v.source.includes('follow') ? v.source.split('_follow_')[0] : v.source)]
      )] // 转附为主，去重
      // .sort((a: string, b: string) => parseInt(b.split('_')[2]) - parseInt(a.split('_')[2])) // 根据创建时间进行排序，越新层级越上 ? 指定 id 不适用

      console.log(ids);

      // 删除操作 删除最上层
      ids[0].includes('polygon') && this.delFeatureById(ids[0] + '_follow_line'); // 多边形 清除附属线
      (ids[0].includes('polygon') || ids[0].includes('polyline')) && this.delFeatureById(ids[0] + '_follow_point') // 多边形和线段 清除附属点
      this.delFeatureById(ids[0]) // 主体
    }

    this.handleChangeCursor(ECursor.NW_RESIZE) // 鼠标指针

    // 绑定事件
    this.events.click = deleteEvent
    this.mapbox.on('click', this.events['click'])
  }

  /****************************************************************
   * state
  ****************************************************************/

  private currentMode: EMode = EMode.STATIC //模式状态

  private drawLayerName: string = '' // name

  private featureStore: FeatureStore = {} // 所有 features

  // 鼠标指针事件
  private cursorEvent = (e: any): void => {
    const layers: Array<string> = Object.keys(this.featureStore) // 读取图层
    if (!!!layers.length) return

    const features = this.mapbox.queryRenderedFeatures(e.point, { layers })
    // console.log(!!features.length);

    !!features.length
      ? this.changeCursor(this.currentCursor.enter) // 进入
      : this.changeCursor(this.currentCursor.leave) // 离开
    // console.log(this.currentCursor)
  }

  // 鼠标指针
  private initCursor = (): { enter: ECursor, leave: ECursor } => {
    return {
      enter: ECursor.POINTER,
      leave: ECursor.DEFAULT,
    }
  }
  private currentCursor = this.initCursor()

  // 绘制过程事件总线
  private initEvents = (): FunctionObj => {
    return <FunctionObj>{
      mousemove: () => { }, // 鼠标移动事件
      contextmenu: this.endDraw, // 鼠标右键事件
      click: this.drawToMap, // 鼠标点击事件
      cursor: this.cursorEvent // 鼠标指针变化
    }
  }
  private events: FunctionObj = this.initEvents()
}